﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using King.Engine.GameObjects;
using Microsoft.Xna.Framework;
using King.Engine.Core;

namespace King.Engine.Components
{

    /// <summary>
    /// Component that will detect when the parent collides with another game object of type T
    /// </summary>
    /// <typeparam name="T">The type of game object which collisions will be detected</typeparam>
    public class CollisionDetector<T> : Component where T: GameObject
    {

        #region Properties

        private HandleCollision<T> HandleCollision;

        #endregion


        #region Constructor

        /// <summary>
        /// Create a collision detector with the given collision callback.
        /// </summary>
        /// <param name="handleCollision">The callback for when a collision is detected</param>
        public CollisionDetector(HandleCollision<T> handleCollision)
        {
            HandleCollision = handleCollision;
        }

        #endregion

        public override void Update(GameTime gameTime)
        {
            var gameObjectsT = ActivityManager.CurrentActivity.GameObjects.OfType<T>();                        
            var collidedObjects = gameObjectsT.Where(t => t!=Parent && Parent.CollidesWith(t));

            foreach (var collidedObject in collidedObjects)
            {
                bool keepSearching = HandleCollision(this.Parent, collidedObject);

                if (!keepSearching)
                {
                    break;
                }
            }
        }

    }

    /// <summary>
    /// Collision detector that will push the parent from collisions. When a collision occurs with a game object of type T,
    /// the parent will be pushed towards its previous location until there is no longer a collision. This is pixel-perfect.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CollisionPusher<T> : CollisionDetector<T> where T : GameObject
    {

        #region Constructor

        public CollisionPusher()
            : base(MoveFromCollision)
        {
        }

        #endregion


        #region Private Methods

        private static bool MoveFromCollision(GameObject self, T other)
        {
            Rectangle targetBounds = self.Bounds;
            Rectangle startBounds = self.PreviousBounds;
            Rectangle obsticleBounds = other.Bounds;

            Rectangle collisionLocation = FindCollisionLocation(obsticleBounds, startBounds, targetBounds);
            
            Rectangle collisionOffsetX = collisionLocation;
            Rectangle collisionOffsetY = collisionLocation;

            collisionOffsetX.X = targetBounds.X;
            collisionOffsetY.Y = targetBounds.Y;

            if (!collisionOffsetX.Intersects(obsticleBounds))
            {
                self.Bounds = collisionOffsetX;
            }
            else if (!collisionOffsetY.Intersects(obsticleBounds))
            {
                self.Bounds = collisionOffsetY;
            }
            else
            {
                self.Bounds = collisionLocation;
            }

            return true;
        }

        private static Rectangle FindCollisionLocation(Rectangle obstacle, Rectangle start, Rectangle target)
        {
            Rectangle midpoint = MidpointOf(start, target);

            if (start == midpoint) return start;

            if (midpoint.Intersects(obstacle))
            {
                return FindCollisionLocation(obstacle, start, midpoint);
            }
            else
            {
                return FindCollisionLocation(obstacle, midpoint, target);
            }
        }

        private static Rectangle MidpointOf(Rectangle A, Rectangle B)
        {
            int x = (B.X - A.X) / 2;
            int y = (B.Y - A.Y) / 2;
            A.Offset(x, y);

            return A;
        }

        #endregion
    }

    /// <summary>
    /// A callback for when a collision is detected.
    /// </summary>
    /// <typeparam name="T">The type of game object which collisions will be detected</typeparam>
    /// <param name="self">Reference to the parent of the collision detector</param>
    /// <param name="other">The collided object</param>
    /// <returns>
    /// Return true if the collision detector should continue searching for collisions in the current update.
    /// Retrun false if the collision detector should stop searching for collisions in the current update
    /// </returns>
    public delegate bool HandleCollision<T>(GameObject self, T other);
}
